/**
 * 自定义Promise函数模块: IIFE ???
 */


(function (params) {

  // 定义状态常量, 注意常量都是大写的 | 和定位的位置
  const PENDING = 'pending'
  const RESOLVED = 'resolved'
  const REJECTED = 'rejected'

  class Promise {
    /**
     * Promise构造函数
     *
     * @constructor
     * @param executor 执行器函数 | 同步执行函数
     */
    constructor(executor) {
      /**
       * ES5写法, 把this保存到一个变量中, 然后将将所有使用this的地方换成 _this
       */
      const _this = this
      // 给promise对象指定status属性, 初始值为pending
      this.status = PENDING
      // 给promise对象指定一个用于存储数据的属性
      this.data = undefined
      // 每个元素的结构: { onResolved() {}, onRejected() {} }
      this.callbacks = []


      // 构造函数内部定义的resolve和reject函数
      /**
       * ES6写法
       * Tips: 使用箭头函数 const resolve = (value) => {} 代替 function resolve(value){} 定义函数
       * 箭头函数方式定义函数, 里面的this是指定Promise构造函数, 而第二种方式内this指定window
       */
      const resolve = (value) => {
        // 如果当前状态不是pending, 直接return
        if (this.status !== PENDING) return

        // 将状态改为resolved
        this.status = RESOLVED
        // 保存value的数据
        this.data = value
        // 如果有待执行的callback函数, 立即异步执行回调函数onResolved
        if (this.callbacks.length > 0) {
          setTimeout(() => {    // 放入队列中执行所有成功的回调
            this.callbacks.forEach(callbacksObj => {
              callbacksObj.onResolved(value)
            })
          })
        }
      }

      const reject = (reason) => {
        // 如果当前状态不是pending, 直接return
        if (this.status !== PENDING) return

        // 将状态改为rejected
        this.status = REJECTED
        // 保存value的数据
        this.data = reason
        // 如果有待执行的callback函数, 立即异步执行回调函数onRejected
        if (this.callbacks.length > 0) {
          setTimeout(() => {    // 放入队列中执行所有失败的回调
            this.callbacks.forEach(callbacksObj => {
              callbacksObj.onRejected(reason)
            })
          })
        }
      }

      // 立即同步执行executor
      try {
        executor(resolve, reject)
      } catch (e) {
        // 如果执行器抛出, 直接调用reject方法, 将promise对象变为rejected状态
        reject(e)
      }
    }

    /**
     * Promise函数对象的all() | Tips 类对象的方法需要用到static, 跟Java中静态方法类似
     *
     * @param promises  promise实例对象数组, Tips: 里面可以不是promise实例对象
     * @return 返回一个promise,只有当所有promise都成功才会成功,否则只要有一个失败就失败
     */
    static all = function (promises) {
      // 用来保存所有成功value的数据, 怎么样创建数组时指定长度, 使用Array构造函数
      const values = new Array(promises.length)
      // 用于保存成功promise的数量
      let resolveCount = 0
      return new Promise((resolve, reject) => {
          // 遍历promises获取每个promise的结果
          promises.forEach((p, index) => {
            // 确保p是Promise数据类型 | (1)判断类型 (2) Promise.resolve()包一层(省去了判断)
            Promise.resolve(p).then(value => {
              resolveCount++
              // p成功, 将成功的value保存values
              values[index] = value
              // 不能这样写, 因为这样的话, 先成功的回调函数先放, 后成功后方放 --> [空属性 × 3, 2, 3, 5] 现象
              // values.push(value)

              // 如果全部成功了, 将return的promise改变成功
              if (resolveCount === promises.length) {
                resolve(values)
              }
            }, reason => {
              // 只要有一个失败了, return的promise就失败了
              reject(reason)
            })
          })
        }
      )
    }

    /**
     * Promise函数对象的race() 任意一个promise出现成功或失败, 就直接resolve()/reject()
     *
     * @param promises promise实例对象数组
     * @return 返回一个promise, 其结果有第一个完成的promise决定
     */
    static race = function (promises) {
      return new Promise((resolve, reject) => {
          // 遍历promises获取每个promise的结果
          promises.forEach(p => {
            Promise.resolve(p).then(value => {
              // 一旦有成功了, 直接return变为成功
              resolve(value)
            }, reason => {
              // 一旦有失败了, 直接return变为成功
              reject(reason)
            })
          })
        }
      )
    }

    /**
     * new Promise((resolve, reject) => resolve()) 语法糖
     *
     * @param value
     * @return 返回一个成功/失败的promise
     */
    static resolve = function (value) {
      return new Promise((resolve, reject) => {
        if (value instanceof Promise) {
          // 使用value的结果作为promise的结果
          value.then(val => resolve(val), reason => reject(reason))
        } else {
          resolve(value)
        }
      })
    }

    /**
     * new Promise((resolve, reject) => reject()) 语法糖
     *
     * @param reason
     * @return 返回一个指定reason的失败的promise,不需要判断reason的数据类型
     */
    static reject = function (reason) {
      return new Promise((resolve, reject) => {
        reject(reason)
      })
    }

    /**
     * 返回一个promise对象, 它在指定的时间后才确定结果
     * @param value
     * @param time 延迟时间
     * @return promise对象
     */
    static resolveDelay = function (value, time) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (value instanceof Promise) {
            value.then(val => resolve(val), reason => reject(reason))
          } else {
            resolve(value)
          }
        }, time)
      })
    }

    /**
     * 返回一个promise对象, 它在指定的时间后才失败
     * @param reason
     * @param time 延迟时间
     * @return promise对象
     */
    static rejectDelay = function (reason, time) {
      // 返回一个失败的promise
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject(reason)
        }, time)
      })
    }

    /**
     * Promise原型对象的then() | then方法, new Promise()得到的实例对象可以访问
     *
     * @param onResolved  指定成功的回调函数
     * @param onRejected  指定失败的回调函数
     * @return 返回一个新的promise对象
     */
    then(onResolved, onRejected) {
      // 向后传递成功的value  --> 效果实现catch语句, 可以向后传递成功value
      onResolved = typeof onResolved === 'function' ? onResolved : value => value

      // 向后传递失败的reason  | [必须是函数, 不能是undefined]
      // 指定默认的失败的回调 --- (实现异常穿透关键点)
      onRejected = typeof onRejected === 'function' ? onRejected : reason => {
        throw reason
      }

      // 返回一个新的promise对象
      return new Promise((resolve, reject) => {
        /**
         * 调用指定回调函数处理, 根据执行结果, 改变return的promise的状态
         *
         * @param callback 执行的回调函数 onResolved 或 onRejected
         */
        const handler = (callback) => {
          /**
           * 1.如果执行抛出异常: return的Promise对象状态变为rejected
           * 2.如果回调函数返回的非promise, 新的promise变为resolved
           * 3.如果回调函数返回是另一个新promise, return的promise结果就是这个promise的结果
           */
          try {
            const result = callback(this.data)
            // 使用instanceof, 判断对象是不是这个数据类型 | 语法: 对象 instanceof 构造函数名字
            // 3.如果回调函数返回是另一个新promise, return的promise结果就是这个promise的结果
            if (result instanceof Promise) {
              // result.then(
              //   value => resolve(value),  // 当result成功时, 让return的promise也成功
              //   reason => reject(reason)) // 当result失败时, 让return的promise也失败

              // 上面的简洁写法
              // JS    arr.forEach(console.log)  注意下forEach算子形参3个 value, index, arr
              // Java  arr.forEach(System.out::println)   // 函数引用 (lambda进一步优化)
              result.then(resolve, reject)
            } else {
              // 2.如果回调函数返回的非promise, 新的promise变为resolved
              resolve(result)
            }
          } catch (error) {
            // 1.如果执行抛出异常: return的Promise对象状态变为rejected
            reject(error)
          }
        }


        if (this.status === PENDING) {
          // 假设当前状态还是pending状态, 将回调函数保存起来
          // this.callbacks.push({onResolved, onRejected})

          // Tips: 不仅要保存两个函数, 还要改变then方法返回的新promise的状态
          // 这段代码没有理解
          this.callbacks.push({
            // 放入一个 func(), 而不是func的话, 会执行这个函数, 将函数的返回值传递给push()中
            // 而handler函数的返回值, 是一个新的resolved或rejected状态的promise对象

            // 自定义两个函数
            onResolved(value) {
              handler(onResolved)
            },
            onRejected(reason) {
              handler(onRejected)
            }
          })
        } else if (this.status === RESOLVED) {
          // 如果不是pending状态(状态先改变), 则去异步执行onResolved/onRejected并改变return新promise状态
          setTimeout(() => {
            handler(onResolved)
          })
        } else {    // rejected状态
          setTimeout(() => {
            handler(onRejected)
          })
        }
      })
    }

    /**
     * Promise原型对象的catch()  | Tips: then/catch其实都是定义到类的 prototype 属性上
     *
     * @param onRejected  指定失败的回调函数
     * @return 返回一个新的promise对象
     */
    catch(onRejected) {
      return this.then(undefined, onRejected)
    }
  }


// 向外暴露Promise函数
  window.Promise = Promise
})
(window)
